package com.cdn.auth.aspect;

import com.cdn.auth.anno.CAuth;
import com.cdn.auth.component.IAuth;
import com.cdn.auth.consts.CAuthConst;
import com.cdn.auth.context.UserContext;
import com.cdn.auth.exception.NoPermissionException;
import com.cdn.auth.user.UserDetails;
import com.cdn.auth.user.UserInfo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Map;

/**
 * @author 蔡定努
 * @date 2024/06/01 23:21
 */
@Aspect
@Order(1)
public class AuthAspect {

    @Autowired
    private ApplicationContext applicationContext;
    @Resource
    private HttpServletResponse response;
    private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();


    @Pointcut("@annotation(com.cdn.auth.anno.CAuth) || @within(com.cdn.auth.anno.CAuth)")
    public void pointcut() {
    }


    @Around("pointcut()")
    public Object preAuth(ProceedingJoinPoint point) throws Throwable {
        if (handleAuth(point)) {
            return point.proceed();
        }
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        throw new NoPermissionException("暂无权限");
    }


    private boolean handleAuth(ProceedingJoinPoint point) {
        MethodSignature ms = (MethodSignature) point.getSignature();
        Method method = ms.getMethod();
        // 读取权限注解，优先方法上，没有则读取类
        CAuth preAuth = method.getAnnotation(CAuth.class);
        if (preAuth == null) {
            // 获取类上的注解
            Object target = point.getTarget();
            preAuth = AnnotationUtils.findAnnotation(target.getClass(), CAuth.class);
        }
        // 判断表达式
        String condition = preAuth.value();
        if (StringUtils.hasText(condition)) {
            Expression expression = EXPRESSION_PARSER.parseExpression(getCondition(condition));
            // 方法参数值
            Object[] args = point.getArgs();
            StandardEvaluationContext context = getEvaluationContext(method, args);
            // 获取解析计算的结果
            return Boolean.TRUE.equals(expression.getValue(context, Boolean.class));
        }
        return false;
    }

    /**
     * @param condition
     * @return {@link String }
     */
    private String getCondition(String condition) {
        Map<String, IAuth> beansOfType = applicationContext.getBeansOfType(IAuth.class);
        if (beansOfType.size() == 1) {
            if (condition.startsWith("@")) {
                int i = condition.indexOf(".");
                String substring1 = condition.substring(0, i);
                condition = condition.replaceAll(substring1, CAuthConst.DEFAULT_AUTH_BEAN_DOT);
            } else {
                condition = CAuthConst.DEFAULT_AUTH_BEAN_DOT + condition;
            }
        }
        return condition;
    }

    /**
     * 获取方法上的参数
     *
     * @param method 方法
     * @param args   变量
     * @return {SimpleEvaluationContext}
     */
    private StandardEvaluationContext getEvaluationContext(Method method, Object[] args) {
        // 初始化Spel表达式上下文，并设置 AuthFun
        StandardEvaluationContext context = new StandardEvaluationContext();

        Map<String, IAuth> beansOfType = applicationContext.getBeansOfType(IAuth.class);
        for (Map.Entry<String, IAuth> authEntry : beansOfType.entrySet()) {
            context.setVariable(authEntry.getKey(), authEntry.getValue());
        }

        // 设置表达式支持spring bean
        context.setBeanResolver(new BeanFactoryResolver(applicationContext));
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            context.setVariable(parameters[i].getName(), args[i]);
        }
        return context;
    }



}



